using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Text;
using System.Reflection;
using System.Collections.Generic;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Linq;

namespace Web.Ajax
{
    public class Json : Attribute
    {
        /// <summary>
        /// This only applies when converting to Json.
        /// </summary>
        public bool IgnoreParent = false;

        public bool Ignore = false;

        public bool IgnoreNullMembers = true;

		public Type ExplicitType;

		public string[] IgnoreList;


		private List<string> ignoreListCache;
		private List<string> ignoreList
		{
			get
			{
				if (ignoreListCache == null)
				{
					ignoreListCache = new List<string>();
					ignoreListCache.AddRange(IgnoreList);
					return ignoreListCache;
				}
				return ignoreListCache;
			}
		}



        #region Field
        public class Field
        {
            public string Name;
            public string Value;

            public static string GetValue(List<Field> fields, string Name)
            {
                if (string.IsNullOrEmpty(Name))
                    return null;
                for (int i = 0; i < fields.Count; i++)
                {
                    if (fields[i].Name == Name)
                        return fields[i].Value;
                }
                return null;
            }
        }
        #endregion

        #region ParseJsonObject
        /// <summary>
        /// Parses a json object string  {...} and returns an array of Fields.
        /// </summary>
        /// <param name="Json">The Json string.</param>
        /// <returns>The array of Fields.</returns>
        public static List<Field> ParseJsonObject(string Json)
        {    
            List<Field> Fields = new List<Field>();            
            Json = Json.TrimStart('{');
            if (Json[Json.Length - 1] == '}')
                Json = Json.Remove(Json.Length - 1);
            if (string.IsNullOrEmpty(Json.Trim()))
                return Fields;
            Json += ",";
            int BraceCount = 0;
            int SquareBracketCount = 0;
            int StartIndex = 0;
            bool InString = false;
            char OpeningStringDelimeter='\'';
            bool NextCharIsEscape = false;
            for (int i = 0; i < Json.Length; i++)
            {
                char cur = Json[i];
                if (cur == '\'' || cur == '\"')
                {
                    if (!InString)
                    {
                        OpeningStringDelimeter = cur;
                        InString = true;
                    }
                    else if (InString && cur == OpeningStringDelimeter && !NextCharIsEscape)
                    {
                        InString = !InString;
                    }
                }
                if (cur == '\\')
                {
                    if (OpeningStringDelimeter == '\"' && InString && !NextCharIsEscape)
                        NextCharIsEscape = true;
                    else
                        NextCharIsEscape = false;
                }
                else
                    NextCharIsEscape = false;                
                if (InString)
                    continue;
                if (cur == '{')
                    BraceCount++;
                if (cur == '}')
                    BraceCount--;
                if (cur == '[')
                    SquareBracketCount++;
                if (cur == ']')
                    SquareBracketCount--;
                if (cur == ',' && BraceCount == 0 && SquareBracketCount == 0)
                {
                    string s = Json.Substring(StartIndex, i - StartIndex);
                    int idx = s.IndexOf(':');
                    Field f = new Field();
                    f.Name = s.Substring(0, idx).Trim();
                    f.Value = s.Substring(idx + 1).Trim();
                    Fields.Add(f);
                    StartIndex = i + 1;                   
                }
            }
            return Fields;
        }
        #endregion

        #region ParseJsonArray
        /// <summary>
        /// Parses a Json array [...] and returns an array of strings, one for each element.
        /// </summary>
        /// <param name="Json">The Json string containing the array.</param>
        /// <returns>An array of elements as strings.</returns>
        public static string[] ParseJsonArray(string Json)
        {
            List<string> Elements = new List<string>();
			Json = Json.Trim(' ', '\r', '\n');
			
			
			if (Json.Length > 0 && Json[0] == '[')
				Json = Json.Substring(1);
			if (Json.Length > 1 && Json[Json.Length - 1] == ']')
				Json = Json.Substring(0, Json.Length - 1);

			//Json = Json.TrimStart('[').TrimEnd(']');

			Json = Json.Trim();
			if (string.IsNullOrEmpty(Json))
                return Elements.ToArray();
            Json += ",";
            int BraceCount = 0;
            int SquareBracketCount = 0;
            int StartIndex = 0;
            bool InString = false;
            char OpeningStringDelimeter = '\'';
            bool NextCharIsEscape = false;            
            for (int i = 0; i < Json.Length; i++)
            {
                char cur = Json[i];
                if (cur == '\'' || cur == '\"')
                {
                    if (!InString)
                    {
                        OpeningStringDelimeter = cur;
                        InString = true;
                    }
                    else if (InString && cur == OpeningStringDelimeter && !NextCharIsEscape)
                    {
                        InString = !InString;
                    }
                }
                if (cur == '\\')
                {
                    if (OpeningStringDelimeter == '\"' && InString && !NextCharIsEscape)
                        NextCharIsEscape = true;
                    else
                        NextCharIsEscape = false;
                }
                else
                    NextCharIsEscape = false;  
                if (InString)
                    continue;
                if (cur == '{')
                    BraceCount++;
                if (cur == '}')
                    BraceCount--;
                if (cur == '[')
                    SquareBracketCount++;
                if (cur == ']')
                    SquareBracketCount--;
                if (cur == ',' && BraceCount == 0 && SquareBracketCount == 0)
                {
                    string s = Json.Substring(StartIndex, i - StartIndex);
                    Elements.Add(s.Trim().TrimStart('\'').TrimEnd('\''));
                    StartIndex = i + 1;
                }
            }
            return Elements.ToArray();
        }
        #endregion

        #region ConvertToJson
        /// <summary>
        /// Converts an C# object to Json
        /// </summary>
        /// <param name="o">The object to convert.</param>
        /// <returns>Json string</returns>
        public static string ConvertToJson(object o)
        {
            return ConvertToJson(o, true);
        }

        public static Json GetJsonAttribute(ICustomAttributeProvider c)
        {
            Json[] list = (Json[])c.GetCustomAttributes(typeof(Json), false);
            if (list.Length == 1)
                return list[0];
            return new Json();
        }

        public static string ConvertToJson(object o, bool root)
        {
            return ConvertToJson(o,root,'\"');
        }

		public static string ConvertToJson(object o, bool root, char delimiter)
		{
			return ConvertToJson(o, root, delimiter, null);

		}

        public static string ConvertToString(char delimeter, string s)
        {
            string r= delimeter + s
                            .Replace("\\", "\\\\")
                            .Replace("\r", "\\r")
                            .Replace("\n", "\\n")
                            .Replace("\"", "\\\"")
                            .Replace("\'", "\\\'")
                                + delimeter;
            return r;
        }

        public static string ConvertFromString(string Value)
        {
            if (string.IsNullOrEmpty(Value)||Value == "null")
                return null;
            string r;
            r=TrimQuotes(Value);
            r=r
                            .Replace("#APOS#", "'")
                            .Replace("\\\'", "\'")
                            .Replace("\\\"", "\"")
                            .Replace("\\n", "\n")
                            .Replace("\\r", "\r")
                            .Replace("\\\\", "\\");
            return r;
        }

        /// <summary>
        /// Converts an C# object to Json
        /// </summary>
        /// <param name="o">The object to convert.</param>
        /// <param name="root">Indicates if this is the first recursive call on the string.</param>
        /// <returns>Json string</returns>
        public static string ConvertToJson(object o, bool root, char delimeter, Type ExplicitType)
        {
            StringBuilder retVal = new StringBuilder();
            //Simple types
            if ((o == null)||(o is System.DBNull))
                return "null";
            if (o is DateTime)
            {
                DateTime dt = (DateTime)o;
                if (root)
                    retVal.Append("(");
                retVal.Append("{Day:" + dt.Day.ToString() + ",Month:" + dt.Month.ToString() + ",Year:" + dt.Year.ToString() + 
                    ", Hour:"+dt.Hour.ToString()+", Minute:"+dt.Minute.ToString()+", Second:"+dt.Second.ToString() +
                    ", Milisecond:"+dt.Millisecond.ToString()+"}");
                if (root)
                    retVal.Append(")");
                return retVal.ToString();
                
            }
            if (o is uint || o is int || o is long || o is bool || o is byte || o is double || o is decimal || o is short) 
                return o.ToString().ToLower();
            if (o is Enum || o is System.Guid)
                return delimeter + o.ToString() + delimeter;
            if (o is string)
                return ConvertToString(delimeter, (string)o);
            if (o is System.Web.UI.WebControls.Unit)
                return delimeter + o.ToString() + delimeter;


            //Objects
            Type t = o.GetType();            
            //if (root)
            //    retVal.Append("(");
            if (o is IList)//t.IsArray)
            {
                retVal.Append("[");
                var list = (IList)o;
                for (int i = 0; i < list.Count; i++)
                {
                    if (i > 0)
                        retVal.Append(",");
                    retVal.Append(ConvertToJson(list[i], false));
                }
                retVal.Append("]");                
            }
			else if (t == typeof(Hashtable))
			{
				Hashtable h=(Hashtable)o;
				retVal.Append("{");
				bool comma=false;
				foreach (string key in h.Keys)
				{
					if(key.IndexOf(' ')>-1)
						continue;
					if(comma)
						retVal.Append(",");
					comma=true;
					retVal.Append(key + ":");
					retVal.Append(ConvertToJson(h[key]));
				}
				retVal.Append("}");
			}
            else
            {
                if (t.IsClass)
                {
                    retVal.Append("{");
                    Json j = GetJsonAttribute(t);

					if (j.ExplicitType != null)
						ExplicitType = j.ExplicitType;

                    FieldInfo[] fields = t.GetFields();
                    bool CommaNeeded = false;
                    for (int i = 0; i < fields.Length; i++)
                    {
                        FieldInfo f = fields[i];
                        if (j.IgnoreParent && f.DeclaringType.FullName != t.FullName)
                            continue;

						if (j.IgnoreList != null && j.ignoreList.Contains(f.Name))
							continue;

                        Json fj = GetJsonAttribute(f);
                        if (fj.Ignore)
                            continue;

						if (ExplicitType != null)
						{
							var etl = f.GetCustomAttributes(ExplicitType, false);
							if (etl.Length == 0)
								continue;
						}

                        object Value=f.GetValue(o);
                        if(j.IgnoreNullMembers&&Value==null)
                            continue;

                        if (CommaNeeded)
                            retVal.Append(",");
                        //retVal.Append("\"" + f.Name + "\":");
                        retVal.Append(f.Name + ":");
                        retVal.Append(ConvertToJson(Value, false, delimeter, ExplicitType));
                        CommaNeeded = true;
                    }
                    PropertyInfo[] props = t.GetProperties();
                    for (int i = 0; i < props.Length; i++)
                    {
                        PropertyInfo p = props[i];
                        MethodInfo GetInfo=p.GetGetMethod();
                        if (GetInfo == null)
                            continue;
                        if (j.IgnoreParent && p.DeclaringType.FullName != t.FullName)
                            continue;

						if (j.IgnoreList != null && j.ignoreList.Contains(p.Name))
							continue;

						if (p.PropertyType == typeof(System.Runtime.Serialization.ExtensionDataObject))
							continue;

                        Json pj = GetJsonAttribute(p);
                        if(pj.Ignore)
                            continue;

						if (ExplicitType != null)
						{
							var etl = p.GetCustomAttributes(ExplicitType, false);
							if (etl.Length == 0)
								continue;
						}


                        object Value = p.GetValue(o, null);
                        if (j.IgnoreNullMembers && Value == null)
                            continue;

                        if (CommaNeeded)
                            retVal.Append(",");
                        //retVal.Append("\"" + p.Name + "\":");
                        retVal.Append(p.Name + ":");
						retVal.Append(ConvertToJson(Value, false,delimeter, ExplicitType));
                        CommaNeeded = true;
                    }
                    retVal.Append("}");
                }
            }
            //if (root)
            //    retVal.Append(")");
            return retVal.ToString();
        }
        #endregion

        private static string TrimQuotes(string s)
        {
            if (s != null&&s.Length>1)
            {
                s = s.Trim();
                if(s[0]=='\"'||s[0]=='\'')
                    s=s.Substring(1);
                int idx=s.Length-1;
                if (s[idx] == '\"' || s[idx] == '\'')
                    s=s.Remove(idx);
            }
            return s;
        }

		private static string PrepareNumber(string s)
		{
			s = TrimQuotes(s);
			if (string.IsNullOrEmpty(s))
				s = "0";
			return s;
		}

        #region ConvertFromJson
        /// <summary>
        /// Instanciates an object from a json string.
        /// </summary>
        /// <param name="Type">The fully qualified type of the object.</param>
        /// <param name="Value">The json string.</param>
        /// <returns>The object.</returns>
        public static object ConvertFromJson(Type Type, string Value)
        {
			string field=null;
			try
			{
				if (Value != null)
					Value = Value.Trim();
				if (Type == null)
					return null;
				
				if (Type == typeof(double))
					return Convert.ToDouble(PrepareNumber(Value));
				if (Type == typeof(decimal))
					return Convert.ToDecimal(PrepareNumber(Value));
				if (Type == typeof(short))
					return Convert.ToInt16(PrepareNumber(Value));
				if (Type == typeof(int))
					return Convert.ToInt32(PrepareNumber(Value));
				if (Type == typeof(long))
					return Convert.ToInt64(PrepareNumber(Value));
				if (Type == typeof(uint))
					return Convert.ToUInt32(PrepareNumber(Value));
				if (Type == typeof(ulong))
					return Convert.ToUInt64(PrepareNumber(Value));
				if (Type == typeof(byte))
					return Convert.ToByte(PrepareNumber(Value));
				if (Type == typeof(bool))
					return Convert.ToBoolean(TrimQuotes(Value));
				if (Type == typeof(string))
					return ConvertFromString(Value);
				if (Type.IsEnum)
					return Enum.Parse(Type, TrimQuotes(Value));
				if (Reflection.Type.IsNullable(Type))
				{
					Value = TrimQuotes(Value);
					if (string.IsNullOrEmpty(Value) || Value == "null")
						return null;
					if (Type == typeof(short?))
						return Convert.ToInt16(Value);
					if (Type == typeof(int?))
						return Convert.ToInt32(Value);
					if (Type == typeof(long?))
						return Convert.ToInt64(Value);
					if (Type == typeof(decimal?))
						return Convert.ToDecimal(Value);
					if (Type == typeof(double?))
						return Convert.ToDouble(Value);
					if (Type == typeof(bool?))
						return Convert.ToBoolean(Value);
					if (Type == typeof(byte?))
						return Convert.ToByte(Value);
					if (Type == typeof(uint?))
						return Convert.ToUInt32(Value);
					if (Type == typeof(ulong?))
						return Convert.ToUInt64(Value);
					if(Type.IsGenericType && Type.GetGenericTypeDefinition() == typeof(Nullable<>) && Type.GetGenericArguments().Length>0) 
					{
						if (Type.GetGenericArguments()[0].IsEnum)
						{
							try
							{
								return Enum.Parse(Type.GetGenericArguments()[0], Value);
							}
							catch
							{
								return null;
							}
						}
					}
				}
				if (Type == typeof(DateTime) || Type == typeof(DateTime?))
				{
					try
					{
						if (string.IsNullOrEmpty(Value) || Value == "null")
							throw new Exception("No Date Value.");
						List<Field> Fields = ParseJsonObject(Value);
						string d = TrimQuotes(Field.GetValue(Fields, "Day"));
						string M = TrimQuotes(Field.GetValue(Fields, "Month"));
						string y = TrimQuotes(Field.GetValue(Fields, "Year"));
						string h = TrimQuotes(Field.GetValue(Fields, "Hour"));
						string m = TrimQuotes(Field.GetValue(Fields, "Minute"));
						string s = TrimQuotes(Field.GetValue(Fields, "Second"));
						string ms = TrimQuotes(Field.GetValue(Fields, "Milisecond"));
						DateTime dt;

						if (!string.IsNullOrEmpty(h) && !string.IsNullOrEmpty(m) && !string.IsNullOrEmpty(s) && !string.IsNullOrEmpty(ms))
							dt = new DateTime(int.Parse(y), int.Parse(M), int.Parse(d), int.Parse(h), int.Parse(m), int.Parse(s), int.Parse(ms));
						else if (!string.IsNullOrEmpty(h) && !string.IsNullOrEmpty(m) && !string.IsNullOrEmpty(s))
							dt = new DateTime(int.Parse(y), int.Parse(M), int.Parse(d), int.Parse(h), int.Parse(m), int.Parse(s));
						else
							dt = new DateTime(int.Parse(y), int.Parse(M), int.Parse(d));

						if (Type == typeof(DateTime?))
							return new DateTime?(dt);
						return dt;
					}
					catch
					{
						if (Type == typeof(DateTime?))
							return null;
						return DateTime.MinValue;
					}
				}
				if (Value == null)
					return null;
				if (Value.StartsWith("{"))		//Object
				{
					object o = null;
					List<Field> Fields = ParseJsonObject(Value);

					if (Type == typeof(object))
					{
						var dateFieldNames = new string[] { "Day", "Month", "Year" };
						var datefields = (from f in Fields where dateFieldNames.Contains(f.Name) select f).ToArray();
						if (datefields != null && datefields.Length == 3)
							return ConvertFromJson(typeof(DateTime), Value);
						else
							Type = typeof(Hashtable);
					}

					o = Activator.CreateInstance(Type);
					if (o == null)
						return null;

					for (int i = 0; i < Fields.Count; i++)
					{
						string FieldName = Fields[i].Name;
						field = FieldName;
						FieldName = TrimQuotes(FieldName);
						if (Type == typeof(Hashtable))
						{
							Hashtable hash = (Hashtable)o;
							var v= Fields[i].Value;
							if (v != null)
							{
								if(v.StartsWith("{"))
									hash[FieldName]=ConvertFromJson(typeof(object), v);
								else if(v.StartsWith("["))
									hash[FieldName] = ConvertFromJson(typeof(object[]), v);
								else
									hash[FieldName] = ConvertFromJson(typeof(string), v);
							}
						}
						else
						{
							FieldInfo FieldInfo = Type.GetField(FieldName);
							if (FieldInfo != null && !FieldInfo.IsLiteral)
								FieldInfo.SetValue(o, ConvertFromJson(FieldInfo.FieldType, Fields[i].Value));
							else
							{
								PropertyInfo PropertyInfo = Type.GetProperty(FieldName);
								if (PropertyInfo != null && PropertyInfo.CanWrite)
									PropertyInfo.SetValue(o, ConvertFromJson(PropertyInfo.PropertyType, Fields[i].Value), null);
							}
						}
					}

					return o;
				}
				if (Value.StartsWith("["))		//Array
				{
					ArrayList ArrayList = new ArrayList();
					Type ArrayType = Type.GetElementType();
					if (ArrayType == null)
						ArrayType = typeof(string);
					string[] Elements = ParseJsonArray(Value);
					for (int i = 0; i < Elements.Length; i++)
					{
						ArrayList.Add(ConvertFromJson(ArrayType, Elements[i]));
					}
					return ArrayList.ToArray(ArrayType);
				}
				return null;
			}
			catch(Exception)
			{
				throw new Exception("Faild to Parse Json (Type: '"+ Type.ToString()+"',Field: '"+field+"' ,Value: '"+Value+"')");
			}
        }

		public static T ConvertFrom<T>(string json)
		{
			return (T)ConvertFromJson(typeof(T), json);
		}
        #endregion

        public static string FunctionCallScript(string Name, params object[] Params)
        {
            StringBuilder script = new StringBuilder();
            script.Append(Name);
            script.Append("(");
            for (int i = 0; i < Params.Length; i++)
            {
                if (i > 0)
                    script.Append(",");
                object o=Params[i];
                string s=o as string;
                if (!string.IsNullOrEmpty(s) && s.StartsWith("pre:"))
                {
                    script.Append(s.Substring(4));
                    continue;
                }
                script.Append(ConvertToJson(o,false,'\''));
            }
            script.Append(");");
            return script.ToString();
        }


        public static void Save(string filepath, object o)
        {
			var s = ConvertToJson(o);
            using (StreamWriter writer = new StreamWriter(filepath, false))
            {
                writer.Write(s);
            }
        }

        public static object Load(string filepath, Type t)
        {
            using (StreamReader reader = new StreamReader(filepath))
            {
                string json=reader.ReadToEnd();
                var obj=ConvertFromJson(t, json);
				return obj;
            }
        }

		public static T Load<T>(string filepath)
		{
			return (T)Load(filepath, typeof(T));
		}


        public static DateTime? GetDate(string json)
        {
            if (json != null)
                json = json.TrimStart('(').TrimEnd(')');
            return (DateTime?)Json.ConvertFromJson(typeof(DateTime?),json);
        }
    }
}
